文章作者:Tyan
博客:noahsnail.com | CSDN | 简书
3.6 定制bean特性
3.6.1 生命周期回调
为了与容器中bean生命周期的管理进行交互,你可以实现Spring的InitializingBean和DisposableBean接口。当初始化beans时容器会调用InitializingBean中的afterPropertiesSet()方法,当销毁beans时容器会调用DisposableBean中的destroy()方法,在这两个方法中bean可以执行特定的行为。
在现代Spring应用中,通常认为JSR-250的
@PostConstruct和@PreDestroy注解是最佳实践接收生命周期回调函数的方法。使用这些注解意味着你的bean没有耦合Spring特定的接口。更多细节请看3.9.8小节,”@PostConstruct和@PreDestroy”。如果你不想使用JSR-250注解,但你仍要注意解耦,可以考虑使用对象定义元数据中的初始化方法和销毁方法。
在Spring内部,Spring框架使用BeanPostProcessor实现来处理任何它能发现的回调接口并调用合适的方法。如果你需要定制Spring不能提供的开箱即用的功能或其它生命周期行为,你可以自己实现BeanPostProcessor。更多信息请看3.8小节,”容器扩展点”。
除了初始化回调函数和销毁回调函数之外,Spring管理的对象也可以实现Lifecycle接口,这些对象可以参与容器自身生命周期驱动的启动和关闭过程。
本节描述了生命周期回调接口。
初始化回调函数
org.springframework.beans.factory.InitializingBean接口在容器设置了bean所有的必须属性之后,允许bean执行初始化工作。InitializingBean接口指定了一个方法:
1 | void afterPropertiesSet() throws Exception; |
建议你不使用InitializingBean接口,因为它对代码与Spring进行了不必要的耦合。作为一种替代方法,你可以使用@PostConstruct注解或指定一个POPJO的初始化方法。在基于XML配置元数据的情况下,你可以使用init-method特性来指定方法的名称,方法是没有返回值和参数的。如果使用Java配置,你可以使用@Bean的initMethod特性,请看”接收生命周期回调函数”小节。例如,下面的代码:
1 | <bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/> |
1 | public class ExampleBean { |
等价于:
1 | public class AnotherExampleBean implements InitializingBean { |
但没有与Spring代码耦合。
销毁回调函数
实现org.springframework.beans.factory.DisposableBean接口允许容器包含的bean销毁时调用回调函数。DisposableBean接口指定了一个方法:
1 | void destroy() throws Exception; |
建议你不使用DisposableBean回调接口,因为它对代码与Spring进行了不必要的耦合。作为一种替代方法,你可以使用@PreDestroy注解或指定一个bean定义支持的通用方法。在基于XML配置元数据的情况下,你可以使用<bean/>的destroy-method特性。如果使用Java配置,你可以使用@Bean的destroyMethod特性,请看”接收生命周期回调”小节。例如,下面的定义:
1 | <bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/> |
1 | public class ExampleBean { |
等价于:
1 | <bean id="exampleInitBean" class="examples.AnotherExampleBean"/> |
1 | public class AnotherExampleBean implements DisposableBean { |
但没有与Spring代码耦合。
默认初始化和销毁方法
当你编写初始化回调函数和析构回调函数时,不要使用Spring特定的InitializingBean和DisposableBean回调接口,自己编写方法,方法名通常为init(),initialize(),dispose()等等。理想情况下,这种生命周期回调方法的名称在整个工程中是标准化的,以便所有开发人员使用同样的方法名称,保证一致性。
你可以配置Spring容器查找每个bean的初始化方法和析构方法时的名字。这意味着,作为一个应用开发者,你可以编写应用程序类并使用名为init()的初始化回调方法,而不必在每个bean定义中配置init-method="init"特性。当bean创建时,Spring Ioc容器调用这个方法(按照前面描述的标准生命周期回调约定)。这个功能也强制了初始化方法和析构方法命名规范的一致性。
假设你的初始化回调方法名为init(),析构回调方法名为destroy()。你的类应该与下面例子中的类类似。
1 | public class DefaultBlogService implements BlogService { |
1 | <beans default-init-method="init"> |
位于顶层<beans/>元素中的default-init-method特性,会让Spring IoC容器将beans中的名为init的方法识别为初始化回调方法。当一个bean创建和组装时,如果bean类有这样一个方法,它会在恰当的时间被调用。
在bean被提供了所有依赖之后,Spring容器确保会立刻调用配置的初始化回调方法。因此初始化回调会在原生bean引用上调用,这意味着AOP拦截器等仍不能应用到bean中。首先要完整的创建目标bean,然后才会应用AOP代理(例如)等拦截器链。如果分别定义了目标bean和代理,你的代码甚至能绕过代理直接与原生的目标bean进行交互。将拦截器应用到初始化方法上可能会产生不一致性,因为这样做会使目标bean的生命周期与它的代理/拦截器相耦合,当你的代码与原生目标bean直接进行交互时,语义会变的很奇怪。
组合生命周期机制
从Spring 2.5开始,在控制bean的生命周期行为时,你有三中选择:InitializingBean和DisposableBean回调接口;定制init()和destroy()方法;@PostConstruct和@PreDestroy`注解。在控制一个给定bean时你可以组合这些机制。
如果一个bean配置了多生命周期机制,每种机制配置了一个不同的方法名,那么每一个配置的方法会按照下面的顺序列表来执行。但是如果配置了相同的名字——例如,
init()初始化方法——不止在一个生命周期机制中配置,那么这个方法只能执行一次,像之前所说的那样。
同一个bean配置了多生命周期机制,并有不同的初始化方法,那么调用顺序如下:
先调用有注解
@PostConstruct的方法然后调用
InitializingBean回调接口定义的afterPropertiesSet()方法最好调用定制配置的
init()方法
Destroy methods are called in the same order:
Methods annotated with
@PreDestroydestroy()as defined by theDisposableBeancallback interfaceA custom configured
destroy()method
析构方法按同样的顺序调用:
先调用有
@PreDestroy注解的方法再调用
DisposableBean回调接口定义的destroy()方法最好调用定制配置的
destroy()方法
启动和关闭回调
Lifecycle接口定义了任何对象生命周期都需要的基本方法(例如启动和停止一些背景处理):
1 | public interface Lifecycle { |
任何Spring管理的对象都可以实现那个接口。当ApplicationContext本身收到启动启动和关闭信号时,例如运行时关闭/再启动场景,它将级联调用所有的上下文定义的Lifecycle实现。它通过委托LifecycleProcessor来完成这个功能:
1 | public interface LifecycleProcessor extends Lifecycle { |
注意LifecycleProcessor本身是Lifecycle接口的一个扩展。它也添加了两个其它的方法来响应上下文的再刷新和关闭的。
注意正规的
org.springframework.context.Lifecycle接口只是一个显式启动/关闭通知的协议,并不意味着在上下文刷新时自动启动。考虑实现org.springframework.context.SmartLifecycle接口来实现对指定bean自动启动的细粒度控制(包括启动时期)。请注意停止通知不能保证在销毁之前到来:在正式关闭时,所有的Lifecyclebeans在通常的析构回调传播之前首先会收到停止通知;但是,在上下文使用期间进行热刷新或尝试取消再刷新,只会调用析构方法。
启动和关闭的调用顺序是很重要的。如果任何两个对象间存在一个”depends-on”关系,那么依赖关系将在它的依赖之后开始,在它的依赖之前停止。然而有时直接的依赖关系是未知的。你可能只知道某个类型的对象应该在另一个类型的对象之前启动。在那种情况下,SmartLifecycle接口定义了另一种选择,也就是说getPhase()定义在它的父接口Phased中。
1 | public interface Phased { |
1 | public interface SmartLifecycle extends Lifecycle, Phased { |
当开始时,最低相位的对象先启动,当停止时,最高相位的对象先停止。因此,实现了SmartLifecycle接口,getPhase()方法返回值为Integer.MIN_VALUE的对象将最先启动并最后停止。另一方面,相位值Integer.MAX_VALUE表明对象应该最后启动,最先停止(可能是因为它依赖其它运行的进程)。当考虑相位值时,知道任何没有实现SmartLifecycle接口的Lifecycle对象的默认值为0是很重要的。因此,任何负相位值表示对象应该在那么标准组件之前启动(在它们之后停止),反之为任何正相位值。
正如你看到的,在SmartLifecycle中定义的停止方法接收一个回调函数。任何实现在关闭进程完成之后都必须调用回调的run()方法。当需要时这可以进行异步关闭,因为LifecycleProcessor接口、DefaultLifecycleProcessor接口的默认实现会等待每个阶段的对象组直到达到超时值,然后调用回调函数。默认每个阶段的超时值为30秒。你可以在上下文中通过定义名为”lifecycleProcessor”的bean来覆盖默认的生命周期处理器实例。如果你只想修改超时值,如下定义是足够的:
1 | <bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor"> |
像上面提到的那样,LifecycleProcessor接口为再刷新和上下文的关闭也定义了回调方法。后者会简单的驱动关闭进程就像显式的调用了stop()方法一样,但当上下文关闭时它才会发生。另一方面refresh回调能使SmartLifecycle beans的另一个功能可用。当上下文再刷新时(所有对象已经实例化并初始化),回调函数将被调用,那时默认的生命周期处理器将会检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。如果为true,对象将会在那时启动而不是等待上下文的显式调用或它自己的start()方法(不像上下文再刷新,对于一个标准的上下文实现上下启动不会自动发生)。”phase”值以及”depends-on”关系将决定启动顺序,像上面描述的一样。
在非web应用中妥善的关闭Spring IoC容器
这一节只应用于非web应用。Spring的基于web的
ApplicationContext实现已经有代码来处理当相关的web应用关闭时,妥善关闭Spring IoC容器的问题。
如果你在非web应用环境使用Spring的IoC容器;例如,在一个富桌面客户端环境中,你在JVM中注册一个关闭钩子。这样做确保了妥善的关闭,为了释放所有资源需要调用与单例beans相关的析构方法。当然,你仍然必须正确的配置和实现这些销毁回调函数。
为了注册一个关闭钩子,你可以调用ConfigurableApplicationContext接口中声明的registerShutdownHook()方法:
1 | import org.springframework.context.ConfigurableApplicationContext; |
3.6.2 ApplicationContextAware和BeanNameAware
当ApplicationContext创建一个实现org.springframework.context.ApplicationContextAware接口的对象实例时,这个实例会提供一个ApplicationContext的引用。
1 | public interface ApplicationContextAware { |
因此beans可以以编程方式操纵创建它们的ApplicationContext,通过ApplicationContext接口,或通过将引用抛给这个接口的一个已知子类,例如ConfigurableApplicationContext,它暴露了额外的功能。一个方法是编程式检索其他的bean。有时这个能力是很有用的,但是通常你应该避免使用它,因为它耦合了代码和Spring,不能遵循控制反转的风格,在控制反转中协作者是作为属性提供给beans的。ApplicationContext的其它方法提供了对文件资源的访问,发布应用事件,访问MessageSource的功能。这些额外的特性将在3.15小节『ApplicationContext”的额外能力』中描述。
从Spring 2.5起,自动装配是另一种可替代的获得ApplicationContext引用的方法。『传统的』constructor和byType自动装配模式(如3.4.5小节所述,『自动装配协作者』)可以分别为构造函数参数或setter方法参数提供ApplicationContext类型的依赖。更多的灵活性包括自动装配变量的能力和多参数方法,使用新的基于注解的自动装配特性。如果你这一做的话,ApplicationContext可以被自动装配到变量中,构造函数参数中或方法参数中,如果讨论的变量,构造函数或方法有@Autowired注解,那么可以期望它是ApplicationContext类型。更多信息请看3.9.2小节,@autowired。
当ApplicationContext创建一个实现了org.springframework.beans.factory.BeanNameAware接口的类时,类中有相关的对象定义中定义的名称的引用。
1 | public interface BeanNameAware { |
在正常的bean属性填入之后,回调方法调用,但在初始化回调方法之前,例如InitializingBean的afterPropertiesSet或一个定制的初始化方法。
3.6.3 其它的Aware接口
除了上面讨论的ApplicationContextAware和BeanNameAware之外,Spring给予了一系列Aware接口来允许beans向容器表明它们需要一个确定的基础结构依赖。最重要的Aware接口总结如下——作为一个通用规则,名字是依赖类型的一个很好暗示:
表3.4. Aware接口
| Name | Injected Dependency | Explained in |
|---|---|---|
| ApplicationContextAware | 声明ApplicationContext |
Section 3.6.2, “ApplicationContextAware and BeanNameAware” |
| ApplicationEventPublisherAware | 封装事件发布的ApplicationContext |
Section 3.15, “Additional Capabilities of the ApplicationContext” |
| BeanClassLoaderAware | 用来加载bean的类加载器 | Section 3.3.2, “Instantiating beans” |
| BeanFactoryAware | 声明BeanFactory |
Section 3.6.2, “ApplicationContextAware and BeanNameAware” |
| BeanNameAware | 声明的bean的名字 | Section 3.6.2, “ApplicationContextAware and BeanNameAware” |
| BootstrapContextAware | 容器运行的资源自适应BootstrapContext. 通常只在JCA aware ApplicationContexts可获得 |
Chapter 28, JCA CCI |
| LoadTimeWeaverAware | 加载时为处理类定义定义的weaver | Section 7.8.4, “Load-time weaving with AspectJ in the Spring Framework” |
| MessageSourceAware | 解析消息配置策略 (支持参数化和国际化) | Section 3.15, “Additional Capabilities of the ApplicationContext” |
| NotificationPublisherAware | Spring JMX通知发布器 | Section 27.7, “Notifications” |
| ResourceLoaderAware | 为底层访问资源配置的加载器 | Chapter 4, Resources |
| ServletConfigAware | 容器运行的当前ServletConfig。 仅在web感知的Spring ApplicationContext中有效 |
Chapter 18, Web MVC framework |
| ServletContextAware | 容器运行的当前ServletContext。 仅在web感知的Spring ApplicationContext中有效 |
Chapter 18, Web MVC framework |
注意这些接口的用法将你的代码与Spring进行了捆绑,不符合控制反转的风格。因此,它们是为那么需要以编程方式访问容器的基础结构beans推荐的。